package com.moli.lumpSugar.websocket;

import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.client.naming.NacosNamingService;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Properties;

/**
 * @program: LumpSugarChat
 * @description: 基于Netty实现webSocket服务端
 * @author: zhangjt
 * @create: 2024-08-15 18:11
 **/
@Slf4j
@Configuration
public class NettyWebSocketServer {
    @Value("${service.port}")
    public int WEB_SOCKET_PORT;
    @Value("${spring.cloud.nacos.server-addr}")
    private String NACOS_SERVER_ADDR; // Nacos 注册中心地址
    @Value("${service.nettyName}")
    private String SERVICE_NAME; // 服务名称
    public static final NettyWebSocketServerHandler NETTY_WEB_SOCKET_SERVER_HANDLER = new NettyWebSocketServerHandler();
    // 创建线程池执行器
    private final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    private final EventLoopGroup workerGroup = new NioEventLoopGroup(NettyRuntime.availableProcessors());

    /**
     * 启动 ws server
     *
     * @return
     * @throws InterruptedException
     */
    @PostConstruct
    public void start() throws Exception {
        run();
    }

    /**
     * 销毁
     */
    @PreDestroy
    public void destroy() {
        Future<?> future = bossGroup.shutdownGracefully();
        Future<?> future1 = workerGroup.shutdownGracefully();
        future.syncUninterruptibly();
        future1.syncUninterruptibly();
        log.info("关闭 ws server 成功");
    }

    public void run() throws Exception {
        // 服务器启动引导对象
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //bossGroup：负责接收客户端连接的事件。
        //workerGroup：负责处理已接入的客户端的网络读写事件。
        serverBootstrap.group(bossGroup, workerGroup)
                //NioServerSocketChannel 表示使用 NIO 模型来处理 socket 连接。它是一个适合于处理大量并发连接的高效通道。
                .channel(NioServerSocketChannel.class)
                //.option(ChannelOption.SO_BACKLOG, 128)：设置服务器 socket 的连接请求队列大小为 128。
                //.option(ChannelOption.SO_KEEPALIVE, true)：启用 TCP 连接的保活机制，保持连接不断开，即使在空闲时也会保持连接。
                .option(ChannelOption.SO_BACKLOG, 128)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new LoggingHandler(LogLevel.INFO)) // 为 bossGroup 添加 日志处理器
                //这里为每个新接入的客户端（SocketChannel）设置了一个 ChannelInitializer。
                // ChannelInitializer是 Netty 的一个特殊处理器，用于在连接建立时初始化 ChannelPipeline，并添加需要的处理器。
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        //30秒客户端没有向服务器发送心跳则关闭连接
                        pipeline.addLast(new IdleStateHandler(30000, 0, 0));
                        // 因为使用http协议，所以需要使用http的编码器，解码器
                        pipeline.addLast(new HttpServerCodec());
                        // 以块方式写，添加 chunkedWriter 处理器 用于处理 HTTP 数据的分块传输，特别是在响应较大数据时。它确保大文件数据能够被分段写入并传输。
                        pipeline.addLast(new ChunkedWriteHandler());
                        /**
                         * 说明：
                         *  1. http数据在传输过程中是分段的，HttpObjectAggregator可以把多个段聚合起来；
                         *  2. 这就是为什么当浏览器发送大量数据时，就会发出多次 http请求的原因
                         */
                        pipeline.addLast(new HttpObjectAggregator(8192));
                        //保存用户ip
                        pipeline.addLast(new HttpHeadersHandler());
                        // 使用自定义的处理器，用url传参，实现握手认证
                        pipeline.addLast(new MyHandShakeByUrlHandler());
                        /**
                         * 说明：
                         *  1. 对于 WebSocket，它的数据是以帧frame 的形式传递的；
                         *  2. 可以看到 WebSocketFrame 下面有6个子类
                         *  3. 浏览器发送请求时： ws://localhost:7000/hello 表示请求的uri
                         *  4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议，保持长连接；
                         *      是通过一个状态码 101 来切换的
                         */
                        pipeline.addLast(new WebSocketServerProtocolHandler("/"));
                        // 使用自定义的处理器，用protocols传参，实现握手认证
//                        pipeline.addLast(new MyHandShakeHandler());
                        // 自定义handler ，处理业务逻辑
                        pipeline.addLast(NETTY_WEB_SOCKET_SERVER_HANDLER);
                    }
                });
        // 启动服务器，监听端口，阻塞直到启动成功
        serverBootstrap.bind(WEB_SOCKET_PORT).sync();
        registerService();
    }

    // 获取本机的 IP 地址
    public String getLocalIP() {
        try {
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface networkInterface = networkInterfaces.nextElement();
                Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
                while (inetAddresses.hasMoreElements()) {
                    InetAddress inetAddress = inetAddresses.nextElement();
                    // 选择非本地回环地址的 IP 地址
                    if (!inetAddress.isLoopbackAddress() && inetAddress.isSiteLocalAddress()) {
                        return inetAddress.getHostAddress();
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return "127.0.0.1"; // 默认返回回环地址，防止发生异常
    }

    public void registerService() throws Exception {
        // 获取本机 IP 地址
        String ip = getLocalIP();
        System.out.println("Registering service with IP: " + ip);

        // 创建Nacos客户端
        Properties properties = new Properties();
        properties.put("serverAddr", NACOS_SERVER_ADDR);
        NamingService namingService = new NacosNamingService(properties);

        // 注册服务到 Nacos
        namingService.registerInstance(SERVICE_NAME, ip, WEB_SOCKET_PORT);
        System.out.println("Netty service registered with Nacos at " + ip + ":" + WEB_SOCKET_PORT);
    }
}
